
package org.apache.solr.core;

import java.io.IOException;
import java.text.ParseException;
import java.util.List;
import java.util.Locale;
import org.apache.lucene.index.IndexCommit;
import org.apache.lucene.index.IndexDeletionPolicy;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.schema.DateField;
import org.apache.solr.util.DateMathParser;
import org.apache.solr.util.plugin.NamedListInitializedPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Standard Solr deletion policy that allows reserving index commit points
 * for certain amounts of time to support features such as index replication
 * or snapshooting directly out of a live index directory.
 *
 *
 * @see org.apache.lucene.index.IndexDeletionPolicy
 */
public class SolrDeletionPolicy implements IndexDeletionPolicy, NamedListInitializedPlugin {

    public static Logger log = LoggerFactory.getLogger(SolrCore.class);
    private String maxCommitAge = null;
    private int maxCommitsToKeep = 1;
    private int maxOptimizedCommitsToKeep = 0;

    @Override
    public void init(NamedList args) {

        String keepOptimizedOnlyString = (String)args.get("keepOptimizedOnly");
        String maxCommitsToKeepString = (String)args.get("maxCommitsToKeep");
        String maxOptimizedCommitsToKeepString = (String)args.get("maxOptimizedCommitsToKeep");
        String maxCommitAgeString = (String)args.get("maxCommitAge");

        if(maxCommitsToKeepString != null && maxCommitsToKeepString.trim().length() > 0) {
            maxCommitsToKeep = Integer.parseInt(maxCommitsToKeepString);
        }
        if(maxCommitAgeString != null && maxCommitAgeString.trim().length() > 0) {
            maxCommitAge = "-" + maxCommitAgeString;
        }
        if(maxOptimizedCommitsToKeepString != null && maxOptimizedCommitsToKeepString.trim().length() > 0) {
            maxOptimizedCommitsToKeep = Integer.parseInt(maxOptimizedCommitsToKeepString);
        }

        // legacy support
        if(keepOptimizedOnlyString != null && keepOptimizedOnlyString.trim().length() > 0) {
            boolean keepOptimizedOnly = Boolean.parseBoolean(keepOptimizedOnlyString);
            if(keepOptimizedOnly) {
                maxOptimizedCommitsToKeep = Math.max(maxOptimizedCommitsToKeep, maxCommitsToKeep);
                maxCommitsToKeep = 0;
            }
        }
    }

    static String str(IndexCommit commit) {
        StringBuilder sb = new StringBuilder();
        try {
            sb.append("commit{");

            Directory dir = commit.getDirectory();

            if(dir instanceof FSDirectory) {
                FSDirectory fsd = (FSDirectory)dir;
                sb.append("dir=").append(fsd.getDirectory());
            }
            else {
                sb.append("dir=").append(dir);
            }

            sb.append(",segFN=").append(commit.getSegmentsFileName());
            sb.append(",generation=").append(commit.getGeneration());
            sb.append(",filenames=").append(commit.getFileNames());
        }
        catch(Exception e) {
            sb.append(e);
        }
        return sb.toString();
    }

    static String str(List commits) {

        StringBuilder sb = new StringBuilder();
        sb.append("num=").append(commits.size());

        for(IndexCommit commit : (List<IndexCommit>)commits) {
            sb.append("\n\t");
            sb.append(str(commit));
        }
        return sb.toString();
    }

    /**
     * Internal use for Lucene... do not explicitly call.
     */
    @Override
    public void onInit(List commits) throws IOException {

        log.info("SolrDeletionPolicy.onInit: commits:" + str(commits));
        updateCommits((List<IndexCommit>)commits);
    }

    /**
     * Internal use for Lucene... do not explicitly call.
     */
    @Override
    public void onCommit(List commits) throws IOException {
        log.info("SolrDeletionPolicy.onCommit: commits:" + str(commits));
        updateCommits((List<IndexCommit>)commits);
    }

    private void updateCommits(List<IndexCommit> commits) {
        // to be safe, we should only call delete on a commit point passed to us
        // in this specific call (may be across diff IndexWriter instances).
        // this will happen rarely, so just synchronize everything
        // for safety and to avoid race conditions

        synchronized(this) {
            long maxCommitAgeTimeStamp = -1L;
            IndexCommit newest = commits.get(commits.size() - 1);
            log.info("newest commit = " + newest.getGeneration());

            int singleSegKept = (newest.getSegmentCount() == 1) ? 1 : 0;
            int totalKept = 1;

            // work our way from newest to oldest, skipping the first since we always want to keep it.
            for(int i = commits.size() - 2; i >= 0; i--) {
                IndexCommit commit = commits.get(i);

                // delete anything too old, regardless of other policies
                try {
                    if(maxCommitAge != null) {
                        if(maxCommitAgeTimeStamp == -1) {
                            DateMathParser dmp = new DateMathParser(DateField.UTC, Locale.ROOT);
                            maxCommitAgeTimeStamp = dmp.parseMath(maxCommitAge).getTime();
                        }
                        if(IndexDeletionPolicyWrapper.getCommitTimestamp(commit) < maxCommitAgeTimeStamp) {
                            commit.delete();
                            continue;
                        }
                    }
                }
                catch(ParseException | IOException e) {
                    log.warn("Exception while checking commit point's age for deletion", e);
                }

                if(singleSegKept < maxOptimizedCommitsToKeep && commit.getSegmentCount() == 1) {
                    totalKept++;
                    singleSegKept++;
                    continue;
                }

                if(totalKept < maxCommitsToKeep) {
                    totalKept++;
                    continue;
                }

                commit.delete();
            }

        } // end synchronized
    }

    /*    private String getId(IndexCommit commit) {

     StringBuilder sb = new StringBuilder();
     Directory dir = commit.getDirectory();

     // For anything persistent, make something that will
     // be the same, regardless of the Directory instance.
     if (dir instanceof FSDirectory) {
     FSDirectory fsd = (FSDirectory) dir;
     File fdir = fsd.getDirectory();
     sb.append(fdir.getPath());
     } else {
     sb.append(dir);
     }

     sb.append('/');
     sb.append(commit.getGeneration());
     return sb.toString();
     }*/
    public String getMaxCommitAge() {
        return maxCommitAge;
    }

    public int getMaxCommitsToKeep() {
        return maxCommitsToKeep;
    }

    public int getMaxOptimizedCommitsToKeep() {
        return maxOptimizedCommitsToKeep;
    }

    public void setMaxCommitsToKeep(int maxCommitsToKeep) {
        synchronized(this) {
            this.maxCommitsToKeep = maxCommitsToKeep;
        }
    }

    public void setMaxOptimizedCommitsToKeep(int maxOptimizedCommitsToKeep) {
        synchronized(this) {
            this.maxOptimizedCommitsToKeep = maxOptimizedCommitsToKeep;
        }
    }
}
